/* Copyright (c) 2008-2010, developers of the Ascension Log Visualizer
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom
 * the Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package com.googlecode.logVisualizer.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.java.dev.spellcast.utilities.DataUtilities;
import net.java.dev.spellcast.utilities.UtilityConstants;

import com.googlecode.logVisualizer.logData.turn.SingleTurn;

/**
 * This class is a centralised place to handle access to various useful data
 * tables.
 */
public final class DataTablesHandler {
    private static final Map<String, Integer> skillMPCostMap;

    private static final Map<String, Integer> fullnessHitMap;

    private static final Map<String, Integer> drunkennessHitMap;

    private static final Map<String, Integer> spleenHitMap;

    private static final Map<String, Double> statsItemsMap;

    private static final Set<String> importantItemsSet;

    private static final Set<String> onetimeItemsSet;

    private static final Set<String> semirareSet;

    private static final Set<String> badmoonSet;

    private static final String FLOWERS_FOR_BAD_MOON_ADVENUTRE = "flowers for ";

    static {
        skillMPCostMap = new HashMap<String, Integer>(150);
        fullnessHitMap = new HashMap<String, Integer>(300);
        drunkennessHitMap = new HashMap<String, Integer>(300);
        spleenHitMap = new HashMap<String, Integer>(300);
        statsItemsMap = new HashMap<String, Double>(100);
        importantItemsSet = new HashSet<String>(300);
        onetimeItemsSet = new HashSet<String>(300);
        semirareSet = new HashSet<String>(100);
        badmoonSet = new HashSet<String>(100);

        readFormattedTable(DataUtilities.getReader(UtilityConstants.KOL_DATA_DIRECTORY,
                                                   "skills.txt"),
                           skillMPCostMap,
                           Pattern.compile("(.+)\\s+\\|\\s+(\\d+)"),
                           true);
        readFormattedTable(DataUtilities.getReader(UtilityConstants.KOL_DATA_DIRECTORY,
                                                   "fullness.txt"),
                           fullnessHitMap,
                           Pattern.compile("([.[^\t]]+)\\s+(\\d+)\\s+.+"),
                           true);
        readFormattedTable(DataUtilities.getReader(UtilityConstants.KOL_DATA_DIRECTORY,
                                                   "inebriety.txt"),
                           drunkennessHitMap,
                           Pattern.compile("([.[^\t]]+)\\s+(\\d+)\\s+.+"),
                           true);
        readFormattedTable(DataUtilities.getReader(UtilityConstants.KOL_DATA_DIRECTORY,
                                                   "spleenhit.txt"),
                           spleenHitMap,
                           Pattern.compile("([.[^\t]]+)\\s+(\\d+)\\s+.+"),
                           true);
        readFormattedTable(DataUtilities.getReader(UtilityConstants.KOL_DATA_DIRECTORY,
                                                   "statsItems.txt"),
                           statsItemsMap,
                           Pattern.compile("(.+)\\s+\\|\\s+(\\d+\\.?\\d*)"),
                           false);
        readTable(DataUtilities.getReader(UtilityConstants.KOL_DATA_DIRECTORY, "importantItems.txt"),
                  importantItemsSet);
        readTable(DataUtilities.getReader(UtilityConstants.KOL_DATA_DIRECTORY, "onetimeItems.txt"),
                  onetimeItemsSet);
        readTable(DataUtilities.getReader(UtilityConstants.KOL_DATA_DIRECTORY, "semirares.txt"),
                  semirareSet);
        readTable(DataUtilities.getReader(UtilityConstants.KOL_DATA_DIRECTORY, "badmoon.txt"),
                  badmoonSet);
    }

    private static final void readTable(
                                        final BufferedReader br, final Set<String> savedToSet) {
        String tmpLine;

        try {
            while ((tmpLine = br.readLine()) != null)
                // Ignore empty lines and comments
                if (tmpLine.length() > 0 && !tmpLine.startsWith("//") && !tmpLine.startsWith("#"))
                    savedToSet.add(tmpLine.toLowerCase(Locale.ENGLISH));

            br.close();
        } catch (final IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Currently only Integer and Double are supported by this method for the
     * value of the savedToMap.
     */
    private static final void readFormattedTable(
                                                 final BufferedReader br,
                                                 final Map<String, ? extends Number> savedToMap,
                                                 final Pattern capturePattern,
                                                 final boolean isInteger) {
        String tmpLine;
        // The savedToMap will always contain String->Number pairs.
        @SuppressWarnings("unchecked")
        final Map<String, Number> tmp = (Map<String, Number>) savedToMap;

        try {
            while ((tmpLine = br.readLine()) != null)
                // Ignore empty lines and comments
                if (tmpLine.length() > 0 && !tmpLine.startsWith("//") && !tmpLine.startsWith("#")) {
                    final Matcher m = capturePattern.matcher(tmpLine);
                    if (m.matches()) {
                        final Number n;
                        if (isInteger)
                            n = Integer.valueOf(Integer.parseInt(m.group(2)));
                        else
                            n = Double.valueOf(Double.parseDouble(m.group(2)));
                        tmp.put(m.group(1).toLowerCase(Locale.ENGLISH), n);
                    }
                }

            br.close();
        } catch (final IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * @param skillName
     *            The skill name whose MP cost should be returned.
     * @return The MP cost of the given skill.
     */
    public static int getSkillMPCost(
                                     final String skillName) {
        final Integer mpCost = skillMPCostMap.get(skillName.toLowerCase(Locale.ENGLISH));
        return mpCost != null ? mpCost.intValue() : 0;
    }

    /**
     * @param consumableName
     *            The consumable name whose fullness hit should be returned.
     * @return The fullness hit of the given consumable.
     */
    public static int getFullnessHit(
                                     final String consumableName) {
        final Integer fullnessHit = fullnessHitMap.get(consumableName.toLowerCase(Locale.ENGLISH));
        return fullnessHit != null ? fullnessHit.intValue() : 0;
    }

    /**
     * @param consumableName
     *            The consumable name whose drunkenness hit should be returned.
     * @return The drunkenness hit of the given consumable.
     */
    public static int getDrunkennessHit(
                                        final String consumableName) {
        final Integer drunkennessHit = drunkennessHitMap.get(consumableName.toLowerCase(Locale.ENGLISH));
        return drunkennessHit != null ? drunkennessHit.intValue() : 0;
    }

    /**
     * @param consumableName
     *            The consumable name whose spleen hit should be returned.
     * @return The spleen hit of the given consumable.
     */
    public static int getSpleenHit(
                                   final String consumableName) {
        final Integer spleenHit = spleenHitMap.get(consumableName.toLowerCase(Locale.ENGLISH));
        return spleenHit != null ? spleenHit.intValue() : 0;
    }

    /**
     * @return A list of items and the amount of main substats they give per
     *         turn. The list is sorted from the highest stats per turn to the
     *         lowest.
     */
    public static List<Pair<String, Double>> getStatsItems() {
        final List<Pair<String, Double>> result = new ArrayList<Pair<String, Double>>(statsItemsMap.size());

        for (final String s : statsItemsMap.keySet())
            result.add(Pair.of(s, statsItemsMap.get(s)));
        Collections.sort(result, new Comparator<Pair<String, Double>>() {
            public int compare(
                               final Pair<String, Double> p1, final Pair<String, Double> p2) {
                if (p2.getVar2() > p1.getVar2())
                    return 1;
                else if (p2.getVar2() < p1.getVar2())
                    return -1;
                else
                    return 0;
            }
        });

        return result;
    }

    /**
     * A check for the important items collection. Important items are items
     * that are always listed in textual logs.
     * 
     * @param itemName
     *            The item name which should be checked on whether it is an
     *            important item.
     * @return True if the item is an important item, otherwise false.
     */
    public static boolean isImportantItem(
                                          final String itemName) {
        return importantItemsSet.contains(itemName.toLowerCase(Locale.ENGLISH));
    }

    /**
     * A check for the one-time items collection. One-time items are items that
     * are listed in textual logs only the first time they dropped.
     * 
     * @param itemName
     *            The item name which should be checked on whether it is an
     *            one-time item.
     * @return True if the item is an one-time item, otherwise false.
     */
    public static boolean isOnetimeItem(
                                        final String itemName) {
        return onetimeItemsSet.contains(itemName.toLowerCase(Locale.ENGLISH));
    }

    /**
     * @return A read-only set containing all one-time items.
     */
    public static Set<String> getOnetimeItems() {
        return Collections.unmodifiableSet(onetimeItemsSet);
    }

    /**
     * @param encounter
     *            The single turn which should be checked on whether it is a
     *            semi-rare.
     * @return True if the encounter is a semi-rare, otherwise false.
     */
    public static boolean isSemirareEncounter(
                                              final SingleTurn encounter) {
        return isSemirareEncounter(encounter.getEncounterName());
    }

    /**
     * @param encounterName
     *            The encounter name which should be checked on whether it is a
     *            semi-rare.
     * @return True if the encounter is a semi-rare, otherwise false.
     */
    public static boolean isSemirareEncounter(
                                              final String encounterName) {
        return semirareSet.contains(encounterName.toLowerCase(Locale.ENGLISH));
    }

    /**
     * @param encounter
     *            The single turn which should be checked on whether it is a Bad
     *            Moon adventure.
     * @return True if the encounter is a Bad Moon adventure, otherwise false.
     */
    public static boolean isBadMoonEncounter(
                                             final SingleTurn encounter) {
        return isBadMoonEncounter(encounter.getEncounterName());
    }

    /**
     * @param encounterName
     *            The encounter name which should be checked on whether it is a
     *            Bad Moon adventure.
     * @return True if the encounter is a Bad Moon adventure, otherwise false.
     */
    public static boolean isBadMoonEncounter(
                                             final String encounterName) {
        final String tmp = encounterName.toLowerCase(Locale.ENGLISH);

        return badmoonSet.contains(tmp) ? true : tmp.startsWith(FLOWERS_FOR_BAD_MOON_ADVENUTRE);
    }

    // This class is not to be instanced.
    private DataTablesHandler() {}
}
